<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .red {
            color: red;
        }
        .green {
            color: green;
        }
    </style>
</head>
<body>
    <div id="app"></div>
</body>
<script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script>
<script src="../vuejs/normalizeProp.js"></script>
<script>
    function shouldSetAsProps(key, el) {
        if (key === 'form' && el.tagName === 'INPUT') return false
        return key in el
    }
    function createRenderer (options) {
        const { createElement, setElementText, insert, patchProps } = options
        function render(vnode, container) {
            if (vnode) {
                patch(container._vnode, vnode, container)
            } else {
                if (container._vnode) {
                    // 卸载
                    unmount(container._vnode)
                }
            }
            container._vnode = vnode
        }
        function patch(n1, n2, container) {
            // 区分不同类型的vnode
            // 对比n1和n2的类型
            if (n1 && n1.type !== n2.type) {
                // 如果不一样则卸载旧vnode
                unmount(n1)
                n1 = null
            }
            const { type } = n2
            if (typeof type === 'string') {
                // 没有旧vnode
                if (!n1) {
                    // 直接挂载
                    mountElement(n2, container)
                } else {
                    // n1存在 打补丁/更新
                    patchElement(n1, n2, container)
                }
            } else if (typeof type === 'object') {
                // 如果n2的type是对象类型，则它描述的是组件
            } else if (typeof type === 'xxx') {
                // 处理其他类型的vnode
            }
            
        }
        // 挂载 
        function mountElement(vnode, container) {
            const el = vnode.el = createElement(vnode.type)
            if (vnode.props) {
                for(key in vnode.props) {
                    patchProps(key, el, null, vnode.props[key])
                }
            }
            if (typeof vnode.children === 'string') {
                setElementText(el, vnode.children)
            } else if (Array.isArray(vnode.children)) {
                vnode.children.forEach(n => {
                    patch(null, n, el)
                });
            }
            insert(el, container)
        }
        // 【更新子节点】
        function patchElement(n1, n2) {
            // 走到这里n1跟n2的type是一样的
            const el = n2.el = n1.el

            const oldProps = n1.props || {}
            const newProps = n2.props || {}

            // 1. 更新props
            // 更新新的props
            for(const key in newProps) {
                if (newProps[key] !== oldProps[key]) {
                    patchProps(key, el, oldProps[key], newProps[key])
                }
            }
            // 移除旧的props
            for(const key in oldProps) {
                if(!(key in newProps)) {
                    patchProps(key, el, oldProps[key], null)
                }
            }

            // 更新children
            patchChildren(n1, n2, el)
        }
        // 【更新子节点】
        function patchChildren(n1, n2, el) {
            // 1.新节点是文本
            if (typeof n2.children === 'string') {
                if (Array.isArray(n1.children)) {
                    n1.children.forEach(c => {
                        unmount(c)
                    })
                }

                setElementText(el, n2.children)
            } else if (Array.isArray(n2.children)) {
                // 2.新节点是数组
                if (Array.isArray(n1.children)) {
                    // TODO diff算法

                    // 为了功能可用 暂时先傻瓜式方法解决
                    n1.children.forEach(c => {
                        unmount(c)
                    })
                    // setElementText(el, '')
                    n2.children.forEach(c => {
                        patch(null, c, el)
                    })
                } else {
                    // 旧节点要么是文本节点要么是不存在
                    // 无论哪种情况，只需要将容器清空，逐个挂载新节点即可
                    setElementText(el, '')
                    n2.children.forEach(c => {
                        patch(null, c, el)
                    })
                }
            } else {
                // 3.新节点不存在
                if (Array.isArray(n1.children)) {
                    n1.children.forEach(c => {
                        unmount(c)
                    })
                } else {
                    setElementText(el, '')
                }
                // 如果没有旧节点，新节点也不存在，什么都不做
            }
        }

        // 卸载
        function unmount(vnode) {
            const el = vnode.el
            const parent = el.parentNode
            if (parent) parent.removeChild(el)
        }

        return {
            render,
        }
    }

    const renderer = createRenderer({
        createElement(tag) {
            return document.createElement(tag);
        },
        setElementText(el, text) {
            el.textContent = text;
        },
        insert(el, parent, anchor = null) {
            parent.insertBefore(el, anchor);
        },
        patchProps(key, el, preValue, nextValue) {
            // 事件的处理
            if (/^on/.test(key)) { 
                const name = key.slice(2).toLowerCase()
                const invokers = el._vei || (el._vei = {})
                let invoker = invokers[key]

                if (nextValue) {
                    if (!invoker) {
                        invoker = el._vei[key] = (e) => {
                            // e.timeStamp 事件触发的时间
                            // 如果事件触发的事件早于事件绑定的时间则不执行事件处理函数
                            if (e.timeStamp < invoker.attached) return
                            if (Array.isArray(invoker.value)) {
                                invoker.value.forEach(fn => fn(e))
                            } else {
                                invoker.value(e)
                            }
                        }
                        invoker.value = nextValue
                        el.addEventListener(name, invoker)
                        invoker.attached = performance.now()
                    } else {
                        // invoker意味着更新，只需要更新invoker.value的值即可
                        invoker.value = nextValue
                    }
                } else if(invoker) {
                    el.removeEventListener(name, invoker)
                }
            } else if (key === 'class') {
                el.className = nextValue
            } else if (shouldSetAsProps(key, el)) {
                const type = typeof el[key]
                if (type === 'boolean' && nextValue === '') {
                    el[key] = true
                } else {
                    el[key] = nextValue
                }
            } else {
                el.setAttribute(key, nextValue)
            }
        }
    })

    const { effect, ref } = VueReactivity
    const bool = ref(false)
    // 相同类型的vnode更新子节点
    // effect(() => {
    //     const vnode = {
    //         type: 'div',
    //         props: bool.value ? {
    //             onClick: () => {
    //                 console.log('触发父组件事件==')
    //             }
    //         } : {},
    //         children: [
    //             {
    //                 type: 'h1',
    //                 props: {
    //                     // 点击事件
    //                     onClick: () => {
    //                             bool.value = true
    //                             console.log('click-111')
    //                         },
    //                     class: normalizeClass({ red: true })
    //                 },
    //                 children: 'hello'
    //             },
    //         ]
    //     }
    //     // 挂载
    //     renderer.render(vnode, document.getElementById('app'))
    // })

    // demo 不同类型的vnode更新
    const vnode = {
        type: 'h1',
        props: {
            // 点击事件
            onClick: () => {
                    bool.value = true
                    console.log('click-111')
                },
            class: normalizeClass({ red: true })
        },
        children: 'hello'
    }
    renderer.render(vnode, document.getElementById('app'))

    const vnode2 = {
        type: 'div',
        // type: 'h1',
        props: {
            class: normalizeClass({ red: true })
        },
        children: 'div-div'
    }
    setTimeout(() => {
        renderer.render(vnode2, document.getElementById('app'))
    }, 2000)
    
    // 总结
    // 相同类型的vnode，更新子节点 只需要更新props和children即可
    // 不同类型的vnode，直接卸载旧的vnode，挂载新的vnode

    // 跟新子节点 patchChildren
    // 子节点是文本类型、数组类型、不存在
    // 新旧节点组合共有9种情况，但实际用到的只有3种（对应上述更新子节点中的1、2、3）
</script>
</html>